/**
 *
 * 114所报告
 *
 * Created by nongzhizhong on 2024/06/05
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const BaseReporter = require('../base-report');
const fileModels = require('../../../files');
const tests = require('../../../tests');
const functionModels = require('../../../functions');
const classCreator = require('../base-report-test-case-transformer');
const { versionType } = require('../../../project-versions');
const utility = require('../../../utility');
const logger = require('../../../logger');

class Default114Reporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, __dirname, format);
    this.complete = complete || false;
    this.functionNumber = 0;
    this.data = null;
    this.ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
  }

  async generate(progressIndicator) {
    const result = {
      cachePath: this.outputDir,
    };
    this.overridingGetAreas();
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.data = {
      projectName: this.project.projectName,
      versionName: this.version.versionName,
      userName: this.creator.username,
      isIntegrationTest: this.isIntegrationTest(),
      testcount: 0,
      coverages: [],
      files: [],
      prefix: this.version.testIdentifierRule && this.version.testIdentifierRule.prefix ? this.version.testIdentifierRule.prefix : '',
    };
    if (this.version.coverages) {
      const rootDirectoryFile = fileModels.getJson(await fileModels.getFile(this.version.rootDirectoryId));
      const { coverages } = this.version;
      if (coverages & tests.coverageKind.statement) {
        this.data.coverages.push({
          name: '语句覆盖率',
          kind: tests.coverageKind.statement,
          value: !rootDirectoryFile.statementCoverage ? '/' : this.handleConveragePercent(rootDirectoryFile.statementCoverage),
        });
      }
      if (coverages & tests.coverageKind.branch) {
        this.data.coverages.push({
          name: '分支覆盖率',
          kind: tests.coverageKind.branch,
          value: !rootDirectoryFile.branchCoverage ? '/' : this.handleConveragePercent(rootDirectoryFile.branchCoverage),
        });
      }
      if (coverages & tests.coverageKind.mcdc) {
        this.data.coverages.push({
          name: 'MC/DC覆盖率',
          kind: tests.coverageKind.mcdc,
          value: !rootDirectoryFile.mcdcCoverage ? '/' : this.handleConveragePercent(rootDirectoryFile.mcdcCoverage),
        });
      }
    }
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    this.data.files = (await this.spliteByFile(progressIndicator)).filter((file) => file.functions.length !== 0);
    const fileName = `${this.project.projectName}-${this.version.versionName}.${this.isIntegrationTest() ? '集成测试报告' : '单元测试报告'}`;
    await this.generateReport('file-template', this.data, `${path.join(this.versionDir, '测试报告')}`);
    const uncoveredFilesInfos = this.data.files.filter((file) => file.uncoveredInfo && file.uncoveredInfo.length > 0);
    if (uncoveredFilesInfos.length > 0) {
      await this.generateReport('file-uncovered-template', {
        versionName: this.data.versionName,
        isIntegrationTest: this.data.isIntegrationTest,
        files: uncoveredFilesInfos,
      }, `${path.join(this.versionDir, '问题记录单')}`);
    }
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  // 重写对象的getAreas方法，调整数据顺序
  overridingGetAreas() {
    this.ReportTestCaseTransformer.prototype.getAreas = function getAreas() {
      if (this.stubsData && this.stubsData.length > 0) {
        this.stubsData.forEach((stub) => {
          if (stub.name) {
            stub.name += '打桩';
          }
          if (stub.typeName) {
            stub.typeName = `将${stub.typeName}`;
          }
        });
      }
      return [
        this.stubsData,
        this.objectData,
        this.paramVariablesData,
        this.globalVariablesData,
        this.staticsData,
        this.stubsOutputData,
        this.mallocVariablesData,
        this.fixedAddressExpressionsData,
        this.fixedAddressBasesData,
        this.returnValueData,
        this.objectOutputData,
        this.globalOutputData,
        this.staticsOutputData,
        this.mallocOutputData,
        this.fixedAddressBasesOutputData];
    };
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }

  async spliteByFile(progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const resFiles = await fileModels.getSourceFileByVersionId(this.versionId, null, ['_id', 'fileName', 'path']);
    return this.handleFiles([...resFiles.values()], this.versionDir, progressIndicator);
  }

  async handleFiles(files, versionDir, progressIndicator) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const result = await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
      return result;
    });
  }

  handleConveragePercent(coverage) {
    return `${Math.round(coverage * 10000) / 100}%`;
  }

  async handleFile(file) {
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      functions: [],
      uncoveredInfo: [],
      uncoveredInfoSummary: '',
    };
    const fileFunctionsInfo = await super.handleFile(file, this.versionDir);
    result.functions = (await this.getFunctionDetails(fileFunctionsInfo.functions, fileFunctionsInfo.typeSystem))
      .sort((a, b) => (a.index > b.index ? 1 : -1));
    const uncoveredInfoSummarySet = new Set();
    result.functions.forEach((func) => {
      this.functionNumber += 1;
      func.functionPrefix = `${this.data.prefix}${'0'.repeat(4 - this.functionNumber.toString().length)}${this.functionNumber}`;
      this.data.testcount += func.tests.length;
      const uncoveredInfoSet = new Set();
      if (func.uncoveredInfo.length > 0) {
        func.uncoveredInfo.forEach((item) => {
          uncoveredInfoSummarySet.add(item.kind);
          uncoveredInfoSet.add(`${item.location}行存在${item.kind}不可达`);
        });
      }
      result.uncoveredInfo = Array.from(uncoveredInfoSet);
    });
    if (uncoveredInfoSummarySet.size > 0) {
      result.uncoveredInfoSummary = Array.from(uncoveredInfoSummarySet).toString();
    }
    return result;
  }

  /**
  *  格式化函数全名，包含形参
  * @param {String} functionName 函数名
  * @param {Object} params 函数variable形参
  */
  getFunctonFullName(functionName, params) {
    const regExpr = /(.*)\(.*\)/;
    if (functionName.match(regExpr)) {
      [, functionName] = functionName.match(regExpr);
    }
    if (Object.keys(params).length > 0) {
      const paramsArr = [];
      Object.keys(params).forEach((name) => {
        if (params[name]['@index'] && params[name]['@type']) {
          paramsArr[parseInt(params[name]['@index'], 10)] = `${params[name]['@type']} ${name}`;
        }
      });
      if (paramsArr.length > 0) {
        functionName = `${functionName}(${paramsArr.toString()})`;
      }
    } else {
      functionName += '()';
    }
    return functionName;
  }

  /**
   *  获取函数详情
   * @param {Array[Object]} functions 函数
   * @param {Object} typeSystem 文件类型系统
   */
  async getFunctionDetails(functions, typeSystem) {
    const functionsDetails = [];
    const uncoveredInfo = [];
    await utility.arrayChunkOperation(functions, 10, async (func) => {
      if (func.tests.length === 0) {
        return;
      }
      const cfg = await functionModels.getFunctionCFG(func._id);
      // 语句未覆盖情况
      if (this.version.coverages & tests.coverageKind.statement && cfg.nodes) {
        Object.values(cfg.nodes).forEach((node) => {
          if (!node.testIds || Object.keys(node.testIds).length === 0) {
            uncoveredInfo.push({
              functionName: func.functionName,
              location: node.begin.line === node.end.line ? node.begin.line : `${node.begin.line}-${node.end.line}`,
              kind: '语句',
              desc: '',
            });
          }
        });
      }
      // 分支未覆盖情况
      if (this.version.coverages & tests.coverageKind.branch && cfg.branches) {
        Object.keys(cfg.branches).forEach((branchId) => {
          Object.keys(cfg.branches[branchId].branches).forEach((block) => {
            const { testIds, kind, value } = cfg.branches[branchId].branches[block];
            if (!testIds || Object.keys(testIds).length === 0) {
              const node = cfg.nodes[cfg.branches[branchId].nodeId];
              // eslint-disable-next-line no-nested-ternary
              const line = node && node.begin && node.end ? (node.begin.line === node.end.line ? node.begin.line : `${node.begin.line}-${node.end.line}`) : '';
              uncoveredInfo.push({
                functionName: func.functionName,
                location: line,
                kind: '分支',
                // eslint-disable-next-line no-nested-ternary
                desc: kind === 'BOOLEAN' ? (value === '1' ? 'T' : 'F') : (kind === 'DEFAULT' ? kind : value),
              });
            }
          });
        });
      }
      // mcdc未覆盖情况
      if (this.version.coverages & tests.coverageKind.mcdc && cfg.decisions) {
        Object.values(cfg.decisions).forEach((decision) => {
          if (!decision.cases) {
            return;
          }
          const node = cfg.nodes[decision.nodeId];
          // 算法一
          if (this.version.mcdcKind === tests.mcdcKind.masking) {
            Object.values(decision.cases).forEach((caseItem) => {
              if (!caseItem.testIds || Object.keys(caseItem.testIds).length === 0) {
                // eslint-disable-next-line no-nested-ternary
                const line = node && node.begin && node.end ? (node.begin.line === node.end.line ? node.begin.line : `${node.begin.line}-${node.end.line}`) : '';
                uncoveredInfo.push({
                  functionName: func.functionName,
                  location: line,
                  kind: 'MC/DC',
                  desc: caseItem.condKey.split('').reverse().map((i, idx) => {
                    let str = 'x';
                    if (i === '0') {
                      str = 'f';
                    } else if (i === '1') {
                      str = 't';
                    }
                    if (idx === 0) {
                      str += '-';
                    }
                    return str.toUpperCase();
                  }).join(''),
                });
              }
            });
            // 算法二
          } else if (decision.conditionCoveredPairs) {
            Object.keys(decision.conditionCoveredPairs).forEach((conditionId) => {
              let covered = false;
              decision.conditionCoveredPairs[conditionId].forEach(([i, j]) => {
                if (covered) {
                  return;
                }
                if (!utility.isEmpty(decision.cases[i].testIds)
                  && !utility.isEmpty(decision.cases[j].testIds)
                ) {
                  covered = true;
                }
              });
              if (!covered) {
                // eslint-disable-next-line no-nested-ternary
                const line = node && node.begin && node.end ? (node.begin.line === node.end.line ? node.begin.line : `${node.begin.line}-${node.end.line}`) : '';
                uncoveredInfo.push({
                  functionName: func.functionName,
                  location: line,
                  kind: 'MC/DC',
                  desc: decision.conditions[conditionId],
                });
              }
            });
          }
        });
      }
      const funcVariables = functionModels.getFunctionVariableJson(func.variables);
      const testcasesDetails = await this.getTestDetailsOfFunction(
        typeSystem,
        func,
        funcVariables,
        func.tests,
      );
      const { coverages } = this.version;
      functionsDetails.push({
        functionName: this.getFunctonFullName(func.functionName, funcVariables.variables ? (funcVariables.variables.params || {}) : {}),
        description: func.description,
        index: func.index,
        uncoveredInfo,
        tests: testcasesDetails,
        funcCoverages: [
          {
            name: '语句',
            status: !!(coverages & tests.coverageKind.statement),
            value: (!func.statementCovered && !func.statementTotal) ? '/' : this.handleConveragePercent(func.statementCovered / func.statementTotal),
          },
          {
            name: '分支',
            status: !!(coverages & tests.coverageKind.branch),
            value: (!func.branchCovered && !func.branchTotal) ? '/' : this.handleConveragePercent(func.branchCovered / func.branchTotal),
          },
          {
            name: 'MC/DC',
            status: !!(coverages & tests.coverageKind.mcdc),
            value: (!func.mcdcCovered && !func.mcdcTotal) ? '/' : this.handleConveragePercent(func.mcdcCovered / func.mcdcTotal),
          },
        ],
      });
    });
    return functionsDetails;
  }

  /**
   * 获取测试用例详情
   * @param {Object} typeSystem 文件类型系统
   * @param {Object} func  函数
   * @param {Object} funcVariables 函数variable
   * @param {Array[Object]} testcases 测试用例
   */
  async getTestDetailsOfFunction(typeSystem, func, funcVariables, testcases) {
    const testcasesDetails = [];
    testcases = testcases.sort((a, b) => (a.identifierNumber > b.identifierNumber ? 1 : -1));

    testcases.forEach((testcase, index) => {
      const newTestcase = tests.getJson(testcase, this.i18n);
      const transformer = new this.ReportTestCaseTransformer(
        newTestcase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      try {
        transformer.transform(this.complete);
      } catch (error) {
        logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${testcase.number}:`, error);
      }
      const newInputDetails = [];
      const newOutputDetails = [];
      const regExpr = /第[\d]+次调用/g;
      transformer.input.forEach((input) => {
        // eslint-disable-next-line no-control-regex
        input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        if (input.variable.match(regExpr)) {
          input.variable += '返回值为';
        }
        // 处理变量值显示格式
        if (input.value) {
          input.value = ` = ${input.value}`;
        }
        if (input.category && input.variable) {
          newInputDetails.push({
            category: `${input.category}:`,
            value: '',
            variable: '',
          });
          newInputDetails.push({
            category: '',
            value: input.value,
            variable: input.variable,
          });
        } else {
          newInputDetails.push(input);
        }
      });
      transformer.output.forEach((output) => {
        // eslint-disable-next-line no-control-regex
        output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        // 处理变量值显示格式
        if (output.result) {
          output.result = ` = ${output.result}`;
        }
        if (output.value) {
          output.value = ` = ${output.value}`;
        }
        if (output.category && output.variable) {
          newOutputDetails.push({
            category: `${output.category}:`,
            value: '',
            result: '',
            variable: '',
          });
          newOutputDetails.push({
            category: '',
            value: output.value,
            result: output.result,
            variable: output.variable,
          });
        } else {
          newOutputDetails.push(output);
        }
      });
      testcasesDetails.push({
        testId: testcase._id,
        description: newTestcase.description,
        resultKind: newTestcase.resultKind,
        identifierNumber: `${'0'.repeat(3 - `${index + 1}`.length)}${index + 1}`,
        input: newInputDetails,
        output: newOutputDetails,
      });
    });
    return testcasesDetails;
  }
}

module.exports = Default114Reporter;
